/*
* Copyright (c) 2020 The canvas Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/canvas.git
* Email:          505544956@qq.com
* FileName:       2d.cc
* Create Time:    2020/01/01 10:12:36
* Description:    /
*/

#include <canvas_2d.h>

#include <vector>
#include <string>
#include <fstream>
#include <iostream>

cvs::Size g_widnow_size(800.f, 600.f);

enum class eFileOrBuffer {
  file, buffer
};
struct sImageData {
  std::string file;
  cvs::Rect dst;
  eFileOrBuffer efb;

  sImageData(cvs::dtype x, cvs::dtype y, eFileOrBuffer _efb, const std::string f) {
    dst.x = x; dst.y = y; efb = _efb; file = f;
  }
  sImageData(const cvs::Rect _dst, eFileOrBuffer _efb, const std::string f) {
    dst = _dst; efb = _efb; file = f;
  }
};

struct sBitmapData {
  cvs::c2d::IBitmap *bitmap;
  cvs::Rect dst;

  sBitmapData() : bitmap(0) {}
};

struct sGradientData {
  cvs::c2d::IBrush *fill_brush;
  cvs::c2d::IShape *shape;
};

struct sTextData {
  cvs::c2d::IShapeText *shape_text;
  cvs::c2d::IShapeRect *shape_back;
  cvs::c2d::ISolidColorBrush *brush_back;
  cvs::c2d::ISolidColorBrush *brush_fill;
  cvs::c2d::ISolidColorBrush *brush_stroke;
};

struct sPaintData {
  std::vector<sBitmapData> bitmaps;
  std::vector<cvs::c2d::IShape*> shapes;
  cvs::c2d::IBrush *fill_brush;
  cvs::c2d::IBrush *stroke_brush;
  cvs::c2d::IStrokeStyle *stroke_style;
  cvs::dtype stroke_width;

  std::vector<sGradientData> gradients;
  std::vector<sTextData> texts;

  sPaintData()
    : fill_brush(0)
    , stroke_brush(0)
    , stroke_style(0)
    , stroke_width(1.f) {}
};

////////////////////////
std::string file_content(const char *file) {
  std::string str;
  std::ifstream f;

  f.open(file, std::ios::binary);
  if (f.is_open()) {
    f.seekg(0, std::ios::end);
    auto len = f.tellg();
    f.seekg(0, std::ios::beg);

    if (len > 0) {
      str.resize(len);
      f.read((char*)str.data(), len);
    }
    f.close();
  }
  return std::move(str);
}
cvs::Result create_bitmap(cvs::c2d::ITarget *t, const std::string file, eFileOrBuffer efb, cvs::c2d::IBitmap **bitmap) {
  if (!t) return CVS_E_InvalidParam;
  if (file.empty()) return CVS_E_InvalidParam;
  auto r = t->render();
  if (!r) return CVS_E_Fail;

  cvs::c2d::ISurface *s = 0;
  auto rst = r->create_surface(&s);
  if (cvs::Failed(rst)) return CVS_E_Fail;

  rst = CVS_E_Fail;
  if (efb == eFileOrBuffer::buffer) {
    rst = s->load_buffer(file.c_str(), file.size());
  } else if (efb == eFileOrBuffer::file) {
    rst = s->load_file(file.c_str());
  }

  if (cvs::Succeeded(rst))
    rst = t->create_bitmap(bitmap);

  if (cvs::Succeeded(rst))
    rst = (*bitmap)->create(s);

  cvs::SaveDecref(s);

  return rst;
}
cvs::Result create_bitmap(cvs::c2d::ITarget *t, const sImageData &sid, sBitmapData &sbd) {
  auto rst = create_bitmap(t, sid.file, sid.efb, &sbd.bitmap);
  if (cvs::Failed(rst)) return rst;

  sbd.dst = sid.dst;
  if (sbd.dst.w <= 0.f || sbd.dst.h <= 0.f) {
    auto sz = sbd.bitmap->size();
    sbd.dst.w = sz.w;
    sbd.dst.h = sz.h;
  }

  return rst;
}
cvs::Result create_text(cvs::c2d::ITarget *t, const std::wstring text, const cvs::Rect &rect
          , const cvs::c2d::FontFormat &font, const cvs::c2d::TextFormat &format
          , const cvs::Color &color_back, const cvs::Color &color_fill, const cvs::Color &color_stroke
          , sTextData &td) {
  if (!t) return CVS_E_Fail;
  auto r = t->render();
  if (!r) return CVS_E_Fail;
  
  td.shape_text = 0; td.shape_back = 0;
  td.brush_back = 0; td.brush_fill = 0; td.brush_stroke = 0;

  auto rst = r->create_shape_text(text.c_str(), text.size(), rect, font, format, &td.shape_text);
  if (cvs::Succeeded(rst))
    rst = r->create_shape_rect(td.shape_text->rect(), cvs::Size(), &td.shape_back);
  if (cvs::Succeeded(rst))
    rst = t->create_solid_color_brush(color_back, &td.brush_back);
  if (cvs::Succeeded(rst))
    rst = t->create_solid_color_brush(color_fill, &td.brush_fill);
  if (cvs::Succeeded(rst))
    rst = t->create_solid_color_brush(color_stroke, &td.brush_stroke);
  if (cvs::Failed(rst)) {
    cvs::SaveDecref(td.brush_back);
    cvs::SaveDecref(td.brush_fill);
    cvs::SaveDecref(td.brush_stroke);
    cvs::SaveDecref(td.shape_text);
    cvs::SaveDecref(td.shape_back);
    td.shape_text = 0; td.shape_back = 0;
    td.brush_back = 0; td.brush_fill = 0; td.brush_stroke = 0;
  }
  return rst;
}
////////////////////////
void text_what(cvs::c2d::ITarget *t, std::vector<sTextData> &texts) {
  if (!t) return;
  std::wstring text;
  cvs::Rect rc;
  cvs::c2d::FontFormat font;
  cvs::c2d::TextFormat format;
  sTextData td;
  cvs::Result rst = CVS_S_Ok;

  // 1th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 0.f; rc.w = 150.f; rc.h = 50.f;
  font.family = L"";
  font.size = 25.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Top;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 2th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 60.f; rc.w = 150.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = true;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Center;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 3th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 120.f; rc.w = 150.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = true;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Right;
  format.valign = cvs::c2d::TextAlignVer::Bottom;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 4th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 180.f; rc.w = 150.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = true;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Center;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = true;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 5th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 240.f; rc.w = 150.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = true;
  format.halign = cvs::c2d::TextAlignHor::Center;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = true;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 6th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 270.f; rc.w = 100.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Center;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 7th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 330.f; rc.w = 100.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 8th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 390.f; rc.w = 100.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = true;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = true;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 9th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 450.f; rc.w = 100.f; rc.h = 55.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = true;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = true;
  format.auto_width = false;
  format.auto_height = false;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f, 0.f)
      , cvs::Color(1.f, 1.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 10th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 510.f; rc.w = 100.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = true;
  format.auto_height = true;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);
  
  // 11th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 540.f; rc.w = 100.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::Clip;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = true;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 12th
  text = L"Hello World!";
  rc.x = 0.f; rc.y = 570.f; rc.w = 100.f; rc.h = 50.f;
  font.family = L"";
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Left;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::Ellipsis;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = true;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);

  // 12th
  // unicode code.
  wchar_t tmp_data[10] = { 
    0x00008FD9, 0x0000662F, 0x00004E2D, 0x00006587, 0x00006D4B, 0x00008BD5,
    0x0
  };

  wchar_t tmp_family[10] = {
    0x00004EFF, 0x00009ED1,
    0x0
  };

  text = tmp_data;
  rc.x = 0.f; rc.y = 570.f; rc.w = g_widnow_size.w; rc.h = 50.f;
  font.family = tmp_family;
  font.size = 20.f;
  font.bold = false;
  font.italic = false;
  font.underline = false;
  font.strikethrough = false;
  format.halign = cvs::c2d::TextAlignHor::Center;
  format.valign = cvs::c2d::TextAlignVer::Middle;
  format.direction = cvs::c2d::TextDirection::LeftToRight;
  format.trimming = cvs::c2d::TextTrimming::None;
  format.word_wrap = false;
  format.auto_width = false;
  format.auto_height = true;
  
  rst = create_text(t, text, rc, font, format
      , cvs::Color(1.f, 0.f, 0.f, 0.5f)
      , cvs::Color(0.f, 0.f, 1.f)
      , cvs::Color(1.f, 1.f, 0.f, 0.f)
      , td);
  if (cvs::Succeeded(rst))
    texts.push_back(td);
}

void path_what(cvs::c2d::IShapePath *path) {
  if (!path) return;

  auto rst = path->open(cvs::c2d::FillMode::NoneZero);
  if (cvs::Failed(rst)) return;

  path->begin_figure(cvs::Point(350.f, 150.f));
  path->add_line(cvs::Point(350.f, 250.f));
  path->add_line(cvs::Point(450.f, 200.f));
  path->end_figure(cvs::c2d::FigureEnd::Close);

  path->close();
}
void shape_what(cvs::c2d::IRender *r, std::vector<cvs::c2d::IShape*> &shapes) {
  if (!r) return;

  cvs::Result rst = CVS_S_Ok;

  // line
  cvs::c2d::IShapeLine *line = 0;
  rst = r->create_shape_line(cvs::Point(60.f, 60.f), cvs::Point(160.f, 160.f), &line);
  if (cvs::Succeeded(rst))
    shapes.push_back(line);

  // polyline
  {
    cvs::Point points[] = {
      cvs::Point(170.f, 60.f),
      cvs::Point(170.f, 160.f),
      cvs::Point(270.f, 160.f),
      cvs::Point(220.f, 110.f),
      cvs::Point(270.f, 60.f)
    };
    cvs::c2d::IShapePolyline *polyline = 0;
    rst = r->create_shape_polyline(points, CVS_ArraySize(points), &polyline);
    if (cvs::Succeeded(rst))
      shapes.push_back(polyline);
  }

  // bezier
  {
    cvs::c2d::BezierSegment d;
    d.p0 = cvs::Point(400.f, 100.f);
    d.p1 = cvs::Point(450.f, 0.f);
    d.p2 = cvs::Point(500.f, 200.f);
    d.p3 = cvs::Point(550.f, 100.f);
    cvs::c2d::IShapeBezier *bezier = 0;
    rst = r->create_shape_bezier(d, &bezier);
    if (cvs::Succeeded(rst))
        shapes.push_back(bezier);
  }
  
  // arc
  {
    cvs::c2d::ArcSegment d;
    d.start = cvs::Point(350.f, 60.f);
    d.end = cvs::Point(250.f, 100.f);
    d.size = cvs::Size(50.f, 50.f);
    d.angle = 0;
    d.sweep = true;
    d.large = true;
    cvs::c2d::IShapeArc *arc = 0;
    rst = r->create_shape_arc(d, &arc);
    if (cvs::Succeeded(rst))
      shapes.push_back(arc);
  }
  
  // polygon
  {
    cvs::Point points[] = {
      cvs::Point(60.f, 350.f),
      cvs::Point(160.f, 350.f),
      cvs::Point(110.f, 400.f),
    };
    cvs::c2d::IShapePolygon *polygon = 0;
    rst = r->create_shape_polygon(points, CVS_ArraySize(points), &polygon);
    if (cvs::Succeeded(rst))
      shapes.push_back(polygon);
  }
  
  // rect
  cvs::c2d::IShapeRect *rect = 0;
  rst = r->create_shape_rect(cvs::Rect(60.f, 170.f, 100.f, 100.f), cvs::Size(5.f, 5.f), &rect);
  if (cvs::Succeeded(rst))
    shapes.push_back(rect);
  
  // circle
  cvs::c2d::IShapeCircle *circle = 0;
  rst = r->create_shape_circle(cvs::Point(250.f, 250.f), 50.f, &circle);
  if (cvs::Succeeded(rst))
    shapes.push_back(circle);
  
  // ellipse
  cvs::c2d::IShapeEllipse *ellipse = 0;
  rst = r->create_shape_ellipse(cvs::Point(250.f, 450.f), cvs::Size(100.f, 50.f), &ellipse);
  if (cvs::Succeeded(rst))
    shapes.push_back(ellipse);
  
  // path
  cvs::c2d::IShapePath *path = 0;
  if (cvs::Succeeded(r->create_shape_path(&path))) {
    path_what(path);
    shapes.push_back(path);
  }
}
void image_what(std::vector<sImageData> &imgs) {
  // back
  imgs.push_back(sImageData(
        cvs::Rect(0.f, 0.f, g_widnow_size.w, g_widnow_size.h)
      , eFileOrBuffer::buffer
      , file_content("image/back.jpg")));

  cvs::dtype x, y, w;
  x = 50.f; y = 0.f; w = 48.f; 
  imgs.push_back(sImageData(x, y, eFileOrBuffer::file, "image/chilli.png"));
  imgs.push_back(sImageData(x+w*1, y, eFileOrBuffer::file, "image/cholesterol.png"));
  imgs.push_back(sImageData(x+w*2, y, eFileOrBuffer::buffer, file_content("image/cholesterol-2.png")));
  imgs.push_back(sImageData(x+w*3, y, eFileOrBuffer::file, "image/cucumber.png"));
  imgs.push_back(sImageData(x+w*4, y, eFileOrBuffer::file, "image/carrot.png"));
  imgs.push_back(sImageData(x+w*5, y, eFileOrBuffer::file, "image/artichoke.png"));
  imgs.push_back(sImageData(x+w*6, y, eFileOrBuffer::file, "image/egg-plant.png"));
  imgs.push_back(sImageData(x+w*7, y, eFileOrBuffer::file, "image/leek.png"));
  imgs.push_back(sImageData(x+w*8, y, eFileOrBuffer::file, "image/mushroom.png"));
  imgs.push_back(sImageData(x+w*9, y, eFileOrBuffer::file, "image/peas.png"));
  imgs.push_back(sImageData(x+w*10, y, eFileOrBuffer::file, "image/potato.png"));
  imgs.push_back(sImageData(x+w*11, y, eFileOrBuffer::file, "image/pumpkin.png"));
  imgs.push_back(sImageData(x+w*12, y, eFileOrBuffer::file, "image/radish.png"));
  imgs.push_back(sImageData(x+w*13, y, eFileOrBuffer::file, "image/tomato.png"));
  imgs.push_back(sImageData(x+w*14, y, eFileOrBuffer::file, "image/turnip.png"));
}
void bitmap_brush_what(cvs::c2d::ITarget *t, cvs::c2d::IBitmapBrush *brush) {
  if (!brush || !t) return;

  cvs::c2d::IBitmap *bitmap = 0;
  if (cvs::Failed(create_bitmap(t, "2d/brush.png", eFileOrBuffer::file, &bitmap)))
    return;

  brush->set_bitmap(bitmap);

  cvs::SaveDecref(bitmap);
}
void gradient_what(cvs::c2d::ITarget *t, std::vector<sGradientData> &gradients) {
  if (!t) return;
  auto r = t->render();
  if (!r) return;

  cvs::Result rst = CVS_S_Ok;

  cvs::c2d::IShapeRect *lenear_shape = 0;
  cvs::c2d::IShapeEllipse *radial_shape = 0;
  cvs::c2d::IGradientStopCollection *stop_collection = 0;
  cvs::c2d::ILinearGradientBrush *lenear_brush = 0;
  cvs::c2d::IRadialGradientBrush *radial_brush = 0;

  // shap
  if (cvs::Succeeded(rst))
    rst = r->create_shape_rect(cvs::Rect(400.f, 250.f, 100.f, 100.f), cvs::Size(5.f, 5.f), &lenear_shape);
  if (cvs::Succeeded(rst))
    rst = r->create_shape_ellipse(cvs::Point(600.f, 300.f), cvs::Size(100.f, 50.f), &radial_shape);

  if (cvs::Succeeded(rst)) {
    cvs::c2d::GradientStop stops[] = {
      { 0.f, cvs::Color(1.f, 0.f, 0.f) },
      { 0.5f, cvs::Color(0.f, 1.f, 0.f) },
      { 1.f, cvs::Color(0.f, 0.f, 1.f) },
    };
    rst = t->create_gradient_stop_collection(stops, CVS_ArraySize(stops), cvs::c2d::ExtendMode::Clamp, &stop_collection);
  }

  // linear gradient
  if (cvs::Succeeded(rst)) {
    cvs::c2d::LinearGradientBrushProperties props;
    props.start = cvs::Point(400.f, 250.f);
    props.end = cvs::Point(500.f, 350.f);
    rst = t->create_linear_gradient_brush(props, stop_collection, &lenear_brush);
  }

  if (cvs::Succeeded(rst)) {
    sGradientData sgd;
    sgd.fill_brush = lenear_brush;
    sgd.shape = lenear_shape;
    gradients.push_back(std::move(sgd));
    lenear_brush->incref();
    lenear_shape->incref();
  }

  // radial gradient
  if (cvs::Succeeded(rst)) {
    cvs::c2d::RadialGradientBrushProperties props;
    props.center = cvs::Point(600.f, 300.f);
    props.offset = cvs::Point(0.f, 0.f);
    props.radius_x = 100.f;
    props.radius_y = 50.f;
    rst = t->create_radial_gradient_brush(props, stop_collection, &radial_brush);
  }

  if (cvs::Succeeded(rst)) {
    sGradientData sgd;
    sgd.fill_brush = radial_brush;
    sgd.shape = radial_shape;
    gradients.push_back(std::move(sgd));
    radial_shape->incref();
    radial_brush->incref();
  }

  cvs::SaveDecref(stop_collection);
  cvs::SaveDecref(lenear_brush);
  cvs::SaveDecref(radial_brush);
  cvs::SaveDecref(lenear_shape);
  cvs::SaveDecref(radial_shape);
}

////////////////////////
cvs::Result get_paint_data(cvs::c2d::ITarget *target, sPaintData &pd) {
  if (!target) return CVS_E_Fail;

  cvs::Result rst = CVS_S_Ok;

  pd.fill_brush = 0;
  pd.stroke_brush = 0;

  cvs::c2d::IRender *render = target->render();

  // stroke
  cvs::c2d::ISolidColorBrush *brush = 0;
  rst = target->create_solid_color_brush(cvs::Color(1.f, 0.f, 0.f), &brush);
  if (cvs::Succeeded(rst)) {
    pd.stroke_brush = brush;
    pd.stroke_width = 1.f;
    
    cvs::c2d::StrokeStyleProperties props;
    props.startCap = cvs::c2d::LineCap::Flat;
    props.endCap = cvs::c2d::LineCap::Flat;
    props.dashCap = cvs::c2d::LineCap::Flat;
    props.lineJoin = cvs::c2d::LineJoin::Miter;
    props.dashOffset = 0.f;
    props.miterLimit = 4.f;

    rst = render->create_stroke_style(props, 0, 0, &pd.stroke_style);
  }

  // fill
  cvs::c2d::IBitmapBrush *bitmap_brush = 0;
  rst = target->create_bitmap_brush(&bitmap_brush);
  if (cvs::Succeeded(rst)) {
    bitmap_brush_what(target, bitmap_brush);
    pd.fill_brush = bitmap_brush;
  }


  // bitmap
  {
    std::vector<sImageData> imgs;
    image_what(imgs);
    for (auto img : imgs) {
      sBitmapData sbd;
      if (cvs::Succeeded(create_bitmap(target, img, sbd)))
        pd.bitmaps.push_back(std::move(sbd));
    }
  }

  // shape
  shape_what(render, pd.shapes);

  // gradient
  gradient_what(target, pd.gradients);

  // text
  text_what(target, pd.texts);

  return rst;
}
void free_paint_data(sPaintData &pd) {
  cvs::SaveDecref(pd.fill_brush);
  cvs::SaveDecref(pd.stroke_brush);
  cvs::SaveDecref(pd.stroke_style);
  for (auto p : pd.shapes)
    cvs::SaveDecref(p);
  pd.fill_brush = 0;
  pd.stroke_brush = 0;
  pd.stroke_style = 0;
  pd.shapes.clear();

  for (auto b : pd.bitmaps)
    cvs::SaveDecref(b.bitmap);
  pd.bitmaps.clear();

  for (auto g : pd.gradients) {
    cvs::SaveDecref(g.fill_brush);
    cvs::SaveDecref(g.shape);
  }
  pd.gradients.clear();
}

////////////////////////
cvs::Result draw(cvs::c2d::ITarget *target) {
  if (!target) return CVS_E_Fail;

  cvs::Result rst = CVS_S_Ok;

  sPaintData pd;
  rst = get_paint_data(target, pd);
  if (cvs::Failed(rst))
    return rst;
  
  if (cvs::Succeeded(rst))
    rst = target->begin_draw();
  
  if (cvs::Succeeded(rst)) {
    for (auto b : pd.bitmaps) {
      if (b.bitmap)
        target->draw_bitmap(b.bitmap, &b.dst);
    }
    if (pd.fill_brush) {
      for (auto p : pd.shapes)
        target->fill_shape(p, pd.fill_brush);
    }
    if (pd.stroke_brush) {
      for (auto p : pd.shapes)
        target->draw_shape(p, pd.stroke_brush, pd.stroke_width, pd.stroke_style);
    }
    for (auto g : pd.gradients) {
      if (g.fill_brush && g.shape)
        target->fill_shape(g.shape, g.fill_brush);
    }

    for (auto t : pd.texts) {
      target->fill_shape(t.shape_back, t.brush_back);
      target->fill_shape(t.shape_text, t.brush_fill);
      target->draw_shape(t.shape_text, t.brush_stroke);
    }
  }

  if (cvs::Succeeded(rst))
    rst = target->end_draw();

  free_paint_data(pd);

  return rst;
}

////////////////////////
void load_renders(cvs::IFactory *factory) {
  if (!factory) return;

  factory->load_render("canvas_d2d");
  factory->load_render("canvas_gdip");
  factory->load_render("canvas_sdl");

}

cvs::Result save(cvs::c2d::ISurfaceTarget *target, const char *file, cvs::c2d::SurfaceType type) {
  if (!target || !file || !*file) return CVS_E_InvalidParam;

  cvs::c2d::ISurface *surface = 0;

  cvs::Result rst = target->surface(&surface);
  if (cvs::Failed(rst)) return rst;

  return surface->save(file, type);
}

////////////////////////
int main(int argc, char **argv) {
  cvs::IFactory *factory = 0;
  cvs::c2d::IRender *render = 0;
  cvs::c2d::ISurfaceTarget *target = 0;
  cvs::c2d::IBitmapTarget *bitmap_target = 0;

  cvs::Result rst = cvs::create_factory(&factory);
  if (cvs::Failed(rst)) return -1;

  load_renders(factory);

  rst = factory->create_2d_render(&render);
  if (cvs::Succeeded(rst))
    rst = render->create_surface_target(cvs::c2d::PixelFormat::PBGRA32, g_widnow_size, &target);

  if (cvs::Succeeded(rst)) {
    target->create_compatible_target(&bitmap_target);

    rst = CVS_E_Fail;
    if (bitmap_target) {
      rst = draw(bitmap_target);
      if (cvs::Succeeded(rst)) {
        cvs::c2d::IBitmap *bitmap = 0;
        rst = bitmap_target->bitmap(&bitmap);
        if (cvs::Succeeded(rst))
          rst = target->begin_draw();
        if (cvs::Succeeded(rst))
          rst = target->draw_bitmap(bitmap);
        if (cvs::Succeeded(rst))
          rst = target->end_draw();
        cvs::SaveDecref(bitmap);
      }
    } else {
      rst = draw(target);
    }

    if (cvs::Succeeded(rst))
      save(target, "t.png", cvs::c2d::SurfaceType::Png);
  }

  cvs::SaveDecref(bitmap_target);
  cvs::SaveDecref(target);
  cvs::SaveDecref(render);
  cvs::SaveDecref(factory);

  std::cout << "error code: " << rst << std::endl;

  return 0;
}